package mongorest

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
)

var apiPrefix = "/api/v1/databases/"

var apiRe = regexp.MustCompile("^" + apiPrefix + "([_[:alnum:]]+)/collections/([_[:alnum:]]+)/?([_[:alnum:]]+)?")

type appError struct {
	GoError error
	Message string
	Code    int
}

func (apperr *appError) Error() string {
	first := pretty(apperr)
	if apperr.GoError != nil {
		return apperr.GoError.Error() + first
	}
	return first
}

func notFoundErr(msg string, err error) *appError {
	return &appError{
		GoError: err,
		Message: msg,
		Code:    http.StatusNotFound,
	}
}

func internalErr(msg string, err error) *appError {
	return &appError{
		GoError: err,
		Message: msg,
		Code:    http.StatusInternalServerError,
	}
}

type appHandler func(http.ResponseWriter, *http.Request) *appError

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if apperr := fn(w, r); apperr != nil {
		log.Printf("%s", apperr.Error())
		http.Error(w, apperr.Message, apperr.Code)
	}
}

func setAPIHeaders(writer http.ResponseWriter) {
	h := writer.Header()
	h.Set("Content-Type", "application/json")
	h.Set("Access-Control-Allow-Origin", "*")
	h.Add("Access-Control-Allow-Headers", "X-Requested-With")
}

func parseRecord(reader io.ReadCloser) (Record, *appError) {
	defer reader.Close()
	var rec Record
	err := json.NewDecoder(reader).Decode(&rec)
	if err != nil {
		return nil, internalErr("cannot decode body", err)
	}
	return rec, nil
}

func handleUpdateADocument(writer http.ResponseWriter, req *http.Request, match []string) *appError {
	rec, apperr := parseRecord(req.Body)
	if apperr != nil {
		return apperr
	}
	syncReq := &updateADocRequest{
		Database:   match[1],
		Collection: match[2],
		ID:         match[3],
		Record:     rec,
	}
	err := updateADocument(writer, syncReq)
	if err != nil {
		return internalErr("update a doc failed", err)
	}
	return nil
}

func handleInsertADocument(writer http.ResponseWriter, req *http.Request, match []string) *appError {
	rec, apperr := parseRecord(req.Body)
	if apperr != nil {
		return apperr
	}
	err := insertADocument(writer, &insertADocRequest{
		Database:   match[1],
		Collection: match[2],
		Record:     rec,
	})
	if err != nil {
		return internalErr("insert a doc failed", err)
	}
	return nil
}

func handleListDocuments(writer http.ResponseWriter, req *http.Request, match []string) *appError {
	listReq := &listDocsRequest{
		Database:   match[1],
		Collection: match[2],
	}
	err := listDocuments(writer, listReq)
	if err != nil {
		return internalErr("list docs failed", err)
	}
	return nil
}

func handleOptions(writer http.ResponseWriter, req *http.Request, match []string) *appError {
	return nil
}

/*
Rest JSON API for mongodb databases.

GET - /api/v1/databases/DB/collections/COLLECTION - Get a list of
COLLECTION entities in the DB database.

Request parameters: TODO:

Responses

 200: [DB.COLLECTION entries]
*/

func handleAPI(writer http.ResponseWriter, req *http.Request) *appError {
	match := apiRe.FindStringSubmatch(req.URL.Path)
	setAPIHeaders(writer)
	if match == nil {
		return notFoundErr("404 page not found", nil)
	}
	if match[3] != "" {
		if req.Method == "POST" { // TODO: it was PUT, why?
			return handleUpdateADocument(writer, req, match)
		}
		err := fmt.Errorf("method: %s  path: %s", req.Method, req.URL.Path)
		return notFoundErr("404 page not found - one not supported yet", err)
	}
	if req.Method == "GET" {
		return handleListDocuments(writer, req, match)
	} else if req.Method == "PUT" {
		return handleInsertADocument(writer, req, match)
	} else if req.Method == "OPTIONS" {
		return handleOptions(writer, req, match)
	}
	err := fmt.Errorf("method: %s  path: %s", req.Method, req.URL.Path)
	return notFoundErr("404 page not found - multi not supported yet", err)
}
